using AppUIBasics.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using System.Linq;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;

namespace AppUIBasics.ControlPages
{
    public sealed partial class ItemsRepeaterPage : ItemsPageBase
    {
        private Random random = new Random();
        private int MaxLength = 425;

        public ObservableCollection<Bar> BarItems;
        public MyItemsSource filteredRecipeData = new MyItemsSource(null);
        public List<Recipe> staticRecipeData;
        private bool IsSortDescending = false;

        private double AnimatedBtnHeight;
        private Thickness AnimatedBtnMargin;
        public ItemsRepeaterPage()
        {
            this.InitializeComponent();
            InitializeData();
            repeater2.ItemsSource = Enumerable.Range(0, 500);
        }

        private void InitializeData()
        {
            if (BarItems == null)
            {
                BarItems = new ObservableCollection<Bar>();
            }
            BarItems.Add(new Bar(300, this.MaxLength));
            BarItems.Add(new Bar(25, this.MaxLength));
            BarItems.Add(new Bar(175, this.MaxLength));

            List<object> basicData = new List<object>();
            basicData.Add(64);
            basicData.Add("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
            basicData.Add(128);
            basicData.Add("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.");
            basicData.Add(256);
            basicData.Add("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.");
            basicData.Add(512);
            basicData.Add("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
            basicData.Add(1024);
            MixedTypeRepeater.ItemsSource = basicData;

            List<NestedCategory> nestedCategories = new List<NestedCategory>();

            nestedCategories.Add(new NestedCategory("Fruits", GetFruits()));
            nestedCategories.Add(new NestedCategory("Vegetables", GetVegetables()));
            nestedCategories.Add(new NestedCategory("Grains", GetGrains()));
            nestedCategories.Add(new NestedCategory("Proteins", GetProteins()));

            outerRepeater.ItemsSource = nestedCategories;

            // Set sample code to display on page's initial load
            SampleCodeLayout.Value = @"<muxc:StackLayout x:Name=""VerticalStackLayout"" Orientation=""Vertical"" Spacing=""8""/>";

            SampleCodeDT.Value = @"<DataTemplate x:Key=""HorizontalBarTemplate"" x:DataType=""l:Bar"">
    <Border Background=""{ThemeResource SystemChromeLowColor}"" Width=""{x:Bind MaxLength}"" >
        <Rectangle Fill=""{ThemeResource SystemAccentColor}"" Width=""{x:Bind Length}"" 
                   Height=""24"" HorizontalAlignment=""Left""/> 
    </Border>
</DataTemplate>";

            SampleCodeLayout2.Value = @"<common:ActivityFeedLayout x:Key=""MyFeedLayout"" ColumnSpacing=""12""
                          RowSpacing=""12"" MinItemSize=""80, 108""/>";

            // Initialize list of colors for animatedScrollRepeater
            animatedScrollRepeater.ItemsSource = GetColors();
            animatedScrollRepeater.ElementPrepared += OnElementPrepared;

            // Initialize custom MyItemsSource object with new recipe data
            List<Recipe> RecipeList = GetRecipeList();
            filteredRecipeData.InitializeCollection(RecipeList);
            // Save a static copy to compare to while filtering
            staticRecipeData = RecipeList;
            VariedImageSizeRepeater.ItemsSource = filteredRecipeData;

        }

        private ObservableCollection<string> GetFruits()
        {
            return new ObservableCollection<string> { "Apricots", "Bananas", "Grapes", "Strawberries", "Watermelon", "Plums", "Blueberries" };
        }

        private ObservableCollection<string> GetVegetables()
        {
            return new ObservableCollection<string>{"Broccoli","Spinach","Sweet potato","Cauliflower","Onion", "Brussel sprouts","Carrots"};
        }
        private ObservableCollection<string> GetGrains()
        {
            return new ObservableCollection<string>{"Rice", "Quinoa", "Pasta", "Bread", "Farro", "Oats", "Barley"};
        }
        private ObservableCollection<string> GetProteins()
        {
            return new ObservableCollection<string>{"Steak", "Chicken", "Tofu", "Salmon", "Pork", "Chickpeas", "Eggs"};
        }

        // ==========================================================================
        // Basic, non-interactive ItemsRepeater
        // ==========================================================================
        private void AddBtn_Click(object sender, RoutedEventArgs e)
        {
            BarItems.Add(new Bar(random.Next(this.MaxLength), this.MaxLength));
            DeleteBtn.IsEnabled = true;
        }

        private void DeleteBtn_Click(object sender, RoutedEventArgs e)
        {
            if (BarItems.Count > 0)
            {
                BarItems.RemoveAt(0);
                if (BarItems.Count == 0)
                {
                    DeleteBtn.IsEnabled = false;
                }
            }
        }

        private void RadioBtn_Click(object sender, RoutedEventArgs e)
        {
            string itemTemplateKey = string.Empty;
            var layoutKey = ((FrameworkElement)sender).Tag as string;

            if (layoutKey.Equals(nameof(this.VerticalStackLayout))) // we used x:Name in the resources which both acts as the x:Key value and creates a member field by the same name
            {
                layout.Value = layoutKey;
                itemTemplateKey = "HorizontalBarTemplate";

                repeater.MaxWidth = MaxLength + 12;

                SampleCodeLayout.Value = @"<muxc:StackLayout x:Name=""VerticalStackLayout"" Orientation=""Vertical"" Spacing=""8""/>";
                SampleCodeDT.Value = @"<DataTemplate x:Key=""HorizontalBarTemplate"" x:DataType=""l:Bar"">
    <Border Background=""{ThemeResource SystemChromeLowColor}"" Width=""{x:Bind MaxLength}"" >
        <Rectangle Fill=""{ThemeResource SystemAccentColor}"" Width=""{x:Bind Length}""
                   Height=""24"" HorizontalAlignment=""Left""/> 
    </Border>
</DataTemplate>";
            }
            else if (layoutKey.Equals(nameof(this.HorizontalStackLayout)))
            {
                layout.Value = layoutKey;
                itemTemplateKey = "VerticalBarTemplate";

                repeater.MaxWidth = 6000;

                SampleCodeLayout.Value = @"<muxc:StackLayout x:Name=""HorizontalStackLayout"" Orientation=""Horizontal"" Spacing=""8""/> ";
                SampleCodeDT.Value = @"<DataTemplate x:Key=""VerticalBarTemplate"" x:DataType=""l:Bar"">
    <Border Background=""{ThemeResource SystemChromeLowColor}"" Height=""{x:Bind MaxHeight}"">
        <Rectangle Fill=""{ThemeResource SystemAccentColor}"" Height=""{x:Bind Height}"" 
                   Width=""48"" VerticalAlignment=""Top""/>
    </Border>
</DataTemplate>";
            }
            else if (layoutKey.Equals(nameof(this.UniformGridLayout)))
            {
                layout.Value = layoutKey;
                itemTemplateKey = "CircularTemplate";

                repeater.MaxWidth = 540;

                SampleCodeLayout.Value = @"<muxc:UniformGridLayout x:Name=""UniformGridLayout"" MinRowSpacing=""8"" MinColumnSpacing=""8""/>";
                SampleCodeDT.Value = @"<DataTemplate x:Key=""CircularTemplate"" x:DataType=""l:Bar"">
    <Grid>
        <Ellipse Fill=""{ThemeResource SystemChromeLowColor}"" Height=""{x:Bind MaxDiameter}"" 
                 Width=""{x:Bind MaxDiameter}"" VerticalAlignment=""Center"" HorizontalAlignment=""Center""/>
        <Ellipse Fill=""{ThemeResource SystemAccentColor}"" Height=""{x:Bind Diameter}"" 
                 Width=""{x:Bind Diameter}"" VerticalAlignment=""Center"" HorizontalAlignment=""Center""/>
    </Grid>
</DataTemplate>";
            }
            repeater.Layout = Resources[layoutKey] as Microsoft.UI.Xaml.Controls.VirtualizingLayout;
            repeater.ItemTemplate = Resources[itemTemplateKey] as DataTemplate;
            repeater.ItemsSource = BarItems;

            elementGenerator.Value = itemTemplateKey;
        }

        // ==========================================================================
        // Virtualizing, scrollable list of items laid out by ItemsRepeater
        // ==========================================================================
        private void LayoutBtn_Click(object sender, RoutedEventArgs e)
        {
            string layoutKey = ((FrameworkElement)sender).Tag as string;

            repeater2.Layout = Resources[layoutKey] as Microsoft.UI.Xaml.Controls.VirtualizingLayout;

            layout2.Value = layoutKey;

            if (layoutKey == "UniformGridLayout2")
            {
                SampleCodeLayout2.Value = @"<muxc:UniformGridLayout x:Key=""UniformGridLayout2"" MinItemWidth=""108"" MinItemHeight=""108""
                   MinRowSpacing=""12"" MinColumnSpacing=""12""/>";
            }
            else if (layoutKey == "MyFeedLayout")
            {
                SampleCodeLayout2.Value = @"<common:ActivityFeedLayout x:Key=""MyFeedLayout"" ColumnSpacing=""12""
                          RowSpacing=""12"" MinItemSize=""80, 108""/>";
            }
        }

        // ==========================================================================
        // Animated Scrolling ItemsRepeater with Content Sample
        // ==========================================================================

        private IList<string> GetColors()
        {
            // Initialize list of colors for animated scrolling sample
            IList<string> colors = (typeof(Colors).GetRuntimeProperties().Select(c => c.ToString())).ToList();
            for (int i = 0; i < colors.Count(); i++)
            {
                colors[i] = colors[i].Substring(17);

            }

            return colors;

        }
        private void Animated_GotItem(object sender, RoutedEventArgs e)
        {
            var item = sender as FrameworkElement;
            item.StartBringIntoView(new BringIntoViewOptions()
            {
                VerticalAlignmentRatio = 0.5,
                AnimationDesired = true,
            });

            // Update corresponding rectangle with selected color
            Button senderBtn = sender as Button;
            colorRectangle.Fill = senderBtn.Background;
        }


        /* This function occurs each time an element is made ready for use.
         * This is necessary for virtualization. */
        private void OnElementPrepared(Microsoft.UI.Xaml.Controls.ItemsRepeater sender, Microsoft.UI.Xaml.Controls.ItemsRepeaterElementPreparedEventArgs args)
        {
            var item = ElementCompositionPreview.GetElementVisual(args.Element);
            var svVisual = ElementCompositionPreview.GetElementVisual(Animated_ScrollViewer);
            var scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(Animated_ScrollViewer);

            var scaleExpresion = scrollProperties.Compositor.CreateExpressionAnimation();
            scaleExpresion.SetReferenceParameter("svVisual", svVisual);
            scaleExpresion.SetReferenceParameter("scrollProperties", scrollProperties);
            scaleExpresion.SetReferenceParameter("item", item);

            // Scale the item based on the distance of the item relative to the center of the viewport.
            scaleExpresion.Expression = "1 - abs((svVisual.Size.Y/2 - scrollProperties.Translation.Y) - (item.Offset.Y + item.Size.Y/2))*(.25/(svVisual.Size.Y/2))";

            // Animate the item to change size based on distance from center of viewpoint
            item.StartAnimation("Scale.X", scaleExpresion);
            item.StartAnimation("Scale.Y", scaleExpresion);
            var centerPointExpression = scrollProperties.Compositor.CreateExpressionAnimation();
            centerPointExpression.SetReferenceParameter("item", item);
            centerPointExpression.Expression = "Vector3(item.Size.X/2, item.Size.Y/2, 0)";
            item.StartAnimation("CenterPoint", centerPointExpression);
        }

        private void GetButtonSize(object sender, RoutedEventArgs e)
        {
            Button AnimatedBtn = sender as Button;
            AnimatedBtnHeight = AnimatedBtn.ActualHeight;
            AnimatedBtnMargin = AnimatedBtn.Margin;
        }

        private void Animated_ScrollViewer_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
        {
            Button SelectedItem = GetSelectedItemFromViewport() as Button;
            // Update corresponding rectangle with selected color
            colorRectangle.Fill = SelectedItem.Background;
        }

        // Find centerpoint of ScrollViewer
        private double CenterPointOfViewportInExtent()
        {
            return Animated_ScrollViewer.VerticalOffset + Animated_ScrollViewer.ViewportHeight / 2;
        }

        // Find index of the item that's at the center of the viewport
        private int GetSelectedIndexFromViewport()
        {
            int selectedItemIndex = (int)Math.Floor(CenterPointOfViewportInExtent() / ((double)AnimatedBtnMargin.Top + AnimatedBtnHeight));
            selectedItemIndex %= animatedScrollRepeater.ItemsSourceView.Count;
            return selectedItemIndex;
        }

        // Return item that's currently in center of viewport
        private object GetSelectedItemFromViewport()
        {
            var selectedIndex = GetSelectedIndexFromViewport();
            var selectedElement = animatedScrollRepeater.TryGetElement(selectedIndex) as Button;
            return selectedElement;
        }

        // ==========================================================================
        // VariedImageSize Layout with Filtering/Sorting
        // ==========================================================================
        private List<Recipe> GetRecipeList()
        {
            // Initialize list of recipes for varied image size layout sample
            var rnd = new Random();
            List<Recipe> tempList = new List<Recipe>(
                                        Enumerable.Range(0, 1000).Select(k =>
                                            new Recipe
                                            {
                                                Num = k,
                                                Name = "Recipe " + k.ToString(),
                                                Color = GetColors()[(k % 100) + 1]
                                            }));

            foreach (Recipe rec in tempList)
            {
                // Add one food from each option into the recipe's ingredient list and ingredient string
                string fruitOption = GetFruits()[rnd.Next(0, 6)];
                string vegOption = GetVegetables()[rnd.Next(0, 6)];
                string grainOption = GetGrains()[rnd.Next(0, 6)];
                string proteinOption = GetProteins()[rnd.Next(0, 6)];
                rec.Ingredients = "\n" + fruitOption + "\n" + vegOption + "\n" + grainOption + "\n" + proteinOption;
                rec.IngList = new List<string>() { fruitOption, vegOption, grainOption, proteinOption };

                // Add extra ingredients so items have varied heights in the layout
                rec.RandomizeIngredients();
            }

            return tempList;
        }
        private void OnEnableAnimationsChanged(object sender, RoutedEventArgs e)
        {
            VariedImageSizeRepeater.Animator = EnableAnimations.IsChecked.GetValueOrDefault() ? new DefaultElementAnimator() : null;
        }

        public void FilterRecipes_FilterChanged(object sender, RoutedEventArgs e)
        {
            UpdateSortAndFilter();
        }

        private void OnSortAscClick(object sender, RoutedEventArgs e)
        {
            if (IsSortDescending == true)
            {
                IsSortDescending = false;
                UpdateSortAndFilter();
            }
        }

        private void OnSortDesClick(object sender, RoutedEventArgs e)
        {
            if (!IsSortDescending == true)
            {
                IsSortDescending = true;
                UpdateSortAndFilter();
            }
        }

        private void UpdateSortAndFilter()
        {
            // Find all recipes that ingredients include what was typed into the filtering text box
            var filteredTypes = staticRecipeData.Where(i => i.Ingredients.Contains(FilterRecipes.Text, StringComparison.InvariantCultureIgnoreCase));
            // Sort the recipes by whichever sorting mode was last selected (least to most ingredients by default)
            var sortedFilteredTypes = IsSortDescending ?
                filteredTypes.OrderByDescending(i => i.IngList.Count()) :
                filteredTypes.OrderBy(i => i.IngList.Count());
            // Re-initialize MyItemsSource object with this newly filtered data
            filteredRecipeData.InitializeCollection(sortedFilteredTypes);
        }
    }

    public class NestedCategory
    {
        public string CategoryName { get; set; }
        public ObservableCollection<string> CategoryItems { get; set; }
        public NestedCategory(string catName, ObservableCollection<string> catItems)
        {
            CategoryName = catName;
            CategoryItems = catItems;
        }
    }


    public class MyDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate Normal { get; set; }
        public DataTemplate Accent { get; set; }

        protected override DataTemplate SelectTemplateCore(object item)
        {
            if ((int)item % 2 == 0)
            {
                return Normal;
            }
            else
            {
                return Accent;
            }
        }
    }

    public class StringOrIntTemplateSelector : DataTemplateSelector
    {
        // Define the (currently empty) data templates to return
        // These will be "filled-in" in the XAML code.
        public DataTemplate StringTemplate { get; set; }

        public DataTemplate IntTemplate { get; set; }

        protected override DataTemplate SelectTemplateCore(object item)
        {
            // Return the correct data template based on the item's type.
            if (item.GetType() == typeof(string))
            {
                return StringTemplate;
            }
            else if (item.GetType() == typeof(int))
            {
                return IntTemplate;
            }
            else
            {
                return null;
            }
        }
    }

    public class Bar
    {
        public Bar(double length, int max)
        {
            Length = length;
            MaxLength = max;

            Height = length / 4;
            MaxHeight = max / 4;

            Diameter = length / 6;
            MaxDiameter = max / 6;
        }
        public double Length { get; set; }
        public int MaxLength { get; set; }

        public double Height { get; set; }
        public double MaxHeight { get; set; }

        public double Diameter { get; set; }
        public double MaxDiameter { get; set; }
    }

    public class Recipe
    {
        public int Num { get; set; }
        public string Ingredients { get; set; }
        public List<string> IngList { get; set; }
        public string Name { get; set; }
        public string Color { get; set; }
        public int NumIngredients {
            get 
            {
                return IngList.Count();
            }
        }

        public void RandomizeIngredients()
        {
            // To give the items different heights, give recipes random numbers of random ingredients
            Random rndNum = new Random();
            Random rndIng = new Random();

            ObservableCollection<string> extras = new ObservableCollection<string>{
                                                         "Garlic",
                                                         "Lemon",
                                                         "Butter",
                                                         "Lime",
                                                         "Feta Cheese",
                                                         "Parmesan Cheese",
                                                         "Breadcrumbs"};
            for (int i =0; i < rndNum.Next(0,4); i++)
            {
                string newIng = extras[rndIng.Next(0, 6)];
                if (!IngList.Contains(newIng))
                {
                    Ingredients += "\n" + newIng;
                    IngList.Add(newIng);
                }
            }

        }
    }

    // Custom data source class that assigns elements unique IDs, making filtering easier
    public class MyItemsSource : IList, Microsoft.UI.Xaml.Controls.IKeyIndexMapping, INotifyCollectionChanged
    {
        private List<Recipe> inner = new List<Recipe>();

        public MyItemsSource(IEnumerable<Recipe> collection)
        {
            InitializeCollection(collection);
        }

        public void InitializeCollection(IEnumerable<Recipe> collection)
        {
            inner.Clear();
            if (collection != null)
            {
                inner.AddRange(collection);
            }

            if (CollectionChanged != null)
            {
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }

        #region IReadOnlyList<T>
        public int Count => this.inner != null ? this.inner.Count : 0;

        public object this[int index]
        {
            get
            {
                return inner[index] as Recipe;
            }

            set
            {
                inner[index] = (Recipe)value;
            }
        }

        public IEnumerator<Recipe> GetEnumerator() => this.inner.GetEnumerator();

        #endregion

        #region INotifyCollectionChanged
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region IKeyIndexMapping
        public string KeyFromIndex(int index)
        {
            return inner[index].Num.ToString();
        }

        public int IndexFromKey(string key)
        {
           foreach (Recipe item in inner)
           {
                if (item.Num.ToString() == key)
                {
                    return inner.IndexOf(item);
                }
           }
           return -1;
        }

        #endregion

        #region Unused List methods
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        public int Add(object value)
        {
            throw new NotImplementedException();
        }

        public void Clear()
        {
            throw new NotImplementedException();
        }

        public bool Contains(object value)
        {
            throw new NotImplementedException();
        }

        public int IndexOf(object value)
        {
            throw new NotImplementedException();
        }

        public void Insert(int index, object value)
        {
            throw new NotImplementedException();
        }

        public void Remove(object value)
        {
            throw new NotImplementedException();
        }

        public void RemoveAt(int index)
        {
            throw new NotImplementedException();
        }

        public void CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }

        public bool IsFixedSize => throw new NotImplementedException();

        public bool IsReadOnly => throw new NotImplementedException();

        public bool IsSynchronized => throw new NotImplementedException();

        public object SyncRoot => throw new NotImplementedException();

        #endregion
    }
}
